﻿#pragma once

#include <cstdint>
#include <functional>
#include <unordered_map>
#include <unordered_set>
#include <ctime>
#include <string>
#include <stdexcept>
#include "../protocol/protocol_register.hh"
#include "../../corelib/singleton.hpp"
#include "../../corelib/object_pool.hpp"

using EntityID = std::uint64_t;
using EntityType = std::uint32_t;
static constexpr EntityID INVALID_ENTITY_ID = 0;
static constexpr EntityType INVALID_ENTITY_TYPE = 0;
using EntityState = std::uint32_t;
static constexpr EntityState INVALID_ENTITY_STATE = 0;
using MsgMethod = std::function<void(EntityID from, const google::protobuf::Message&)>;
using MsgHandlerMap = std::unordered_map<std::uint32_t, MsgMethod>;
static constexpr std::time_t MAX_PENDING_TIMEOUT = 1000;

class Entity;
class EntityManager;
class EntityFactoryBase;

using namespace kratos;

// The base class of entity factory
class EntityFactoryBase {
public:
    virtual ~EntityFactoryBase() {}
    // Destroy entity
    virtual void destroyEntity(Entity* entity) = 0;
};

// Entity factroy
// T must be derived from entity and offers a static method T::staticType() to return entity type
template <typename T>
class EntityFactory : public EntityFactoryBase {
public:
    EntityFactory() {}
    virtual ~EntityFactory() {}
    // Create a entity with type T
    template <typename...Args>
    T* createEntity(Args...args) {
        auto entity = kratos::corelib::allocate<T>(args...);
        entity->setFactory(this);
        entity->setType(T::staticType());
        return entity;
    }
    // Create a entity with type T
    template <typename...Args>
    T* createEntityByType(EntityType entityType, Args...args) {
        auto entity = kratos::corelib::allocate<T>(args...);
        entity->setFactory(this);
        entity->setType(entityType);
        return entity;
    }
    // Destroy entity
    virtual void destroyEntity(Entity* entity) override {
        kratos::corelib::dispose(dynamic_cast<T*>(entity));
    }
};

// Entity factory reference
#define EntityFactoryRef(T) (*kratos::corelib::Singleton<EntityFactory<T>>::instance())

// Entity
class Entity {
    EntityID id_; // entity ID
    EntityType type_; // entity type
    EntityState state_; // entity state
    corelib::unique_pool_ptr<MsgHandlerMap> handlerMap_; // entity's handler map
    bool resolved_; // Is in pending set?
    TimerHandle pendingTimerID_; // pending timer ID
    EntityFactoryBase* factory_; // factory
    std::size_t retryResolveTimes_; // Resolve expired times
    static constexpr std::size_t MAX_EX_TIMES = 10; // maximum register expired timeout count
    bool disposed_; // The flag of dispose
    bool identified_; // Has valid ID

    Entity(const Entity&) = delete;
    Entity(Entity&&) = delete;
    const Entity& operator=(const Entity&) = delete;

public:
    // dtor
    virtual ~Entity();
    // Dispose
    void dispose();
    // Check
    bool isDisposed();
    // Check
    operator bool();
    // Returns entity ID
    EntityID getID() const;
    // Returns entity type
    EntityType getType() const;
    // Set entity type
    void setType(EntityType type);
    // Set user defined state
    void setState(EntityState state);
    // Returns user defined state
    EntityState getState() const;
    // The identity of entity has been resolved
    // @param entityID entity ID
    // @retval true
    // @retval false fail
    bool resolve(EntityID entityID);
    // Returns resolved flag
    bool isResolved();
    // Registers a message hander
    // @param clazz The object pointer, Class must derived from Entity
    // @param Memfunc Class's member function
    // @retval true
    // @retval false fail
    template <typename Msg, typename Class, typename MemFunc>
    bool registerHandler(Class* clazz, MemFunc handler) {
        if (!handlerMap_) {
            handlerMap_.reset(corelib::allocate<MsgHandlerMap>());
        }
        auto msgID = ProtocolRegisterRef.getMessageIDFromProto<Msg>();
        if (!msgID) {
            errorlog << "Register message handler for entity failed, message name:" << Msg::descriptor()->name() << endlog;
            return false;
        }
        (*handlerMap_)[msgID] = std::bind(handler, clazz, std::placeholders::_1, std::placeholders::_2);
        return true;
    }
    // Un-register message handler for message 'Msg'
    // @retval true
    // @retval false fail
    template <typename Msg>
    bool unregisterHandler() {
        if (!handlerMap_) {
            return true;
        }
        auto msgID = ProtocolRegisterRef.getMessageIDFromProto<Msg>();
        if (!msgID) {
            return false;
        }
        (*handlerMap_).erase(msgID);
        return true;
    }
    // Returns message handler for 'Msg'
    template <typename Msg>
    MsgMethod getHandler() {
        if (!resolved_) {
            return MsgMethod();
        }
        if (!handlerMap_) {
            return MsgMethod();
        }
        auto msgID = ProtocolRegisterRef.getMessageIDFromProto<Msg>();
        if (!msgID) {
            return MsgMethod();
        }
        auto it = handlerMap_->find(msgID);
        if (it == handlerMap_->end()) {
            return MsgMethod();
        }
        return it->second;
    }
    // Returns message handler for 'Msg'
    MsgMethod getHandlerByMessage(const google::protobuf::Message& msg) {
        if (!resolved_) {
            return MsgMethod();
        }
        if (!handlerMap_) {
            return MsgMethod();
        }
        auto msgID = ProtocolRegisterRef.getMessageIDFromMessage(msg);
        if (!msgID) {
            return MsgMethod();
        }
        auto it = handlerMap_->find(msgID);
        if (it == handlerMap_->end()) {
            return MsgMethod();
        }
        return it->second;
    }

protected:
    // Invoking by method resolve when has an identifier
    virtual bool onResolve();

protected:
    // ctor
    Entity();
    // Set factory
    void setFactory(EntityFactoryBase* factory);
    // Returns factory
    EntityFactoryBase* getFactory();
    // Starts pending timer
    void startPendingTimer();
    // Stops pending timer
    void tryStopPendingTimer();
    // Resolve
    void resolve();

    friend class EntityManager;
    template <typename T> friend class EntityFactory;
};

// Can be found by locator
class Addressable : public Entity {
public:
    // Send message to other entity
    // @param other the target entity ID
    // @param msg google::protobuf::Message instance
    virtual bool talk(EntityID other, const google::protobuf::Message& msg) = 0;
};

class AddressEntity : public Addressable {
    AddressEntity(const AddressEntity&) = delete;
    AddressEntity(AddressEntity&&) = delete;
    const AddressEntity& operator=(const AddressEntity&) = delete;

public:
    virtual ~AddressEntity();
    virtual bool talk(EntityID other, const google::protobuf::Message& msg) override;

protected:
    virtual bool onResolve() override;
    AddressEntity();

    friend class EntityManager;
    template <typename T> friend class EntityFactory;
};

#define REGISTER_MEMBER_MSG_HANDLER(Class, Msg)\
    registerHandler<Msg>(this, &Class::on##Msg)

#define DECLARE_MEMBER_MSG_HANDLER(Msg)\
    void on##Msg(EntityID from, const google::protobuf::Message& msg) {\
        on##Msg##_(from, dynamic_cast<const Msg&>(msg));\
    }\
    void on##Msg##_(EntityID from, const Msg& msg);

#define ON_MEMBER_MSG_HANDLER(Class, from, Msg)\
    void Class::on##Msg##_(EntityID from, const Msg& msg)

#define ON_ENTITY_MSG_DISPATCH(type, entityType)\
    void type##Handler::_onPathMessage(PathID, SiteType, type&) {}\
    void type##Handler::_onSessionMessage(SessionID, type&) {}\
    void type##Handler::_onEntityMessage(std::uint64_t from, std::uint64_t to, type& msg) {\
        auto object = EntityManagerRef.getEntityByType<entityType>(to);\
        if (object) {\
            object->on##type##_(from, msg);\
        }\
    }

using EntityMap = std::unordered_map<EntityID, Entity*>;

// Entity manager
class EntityManager {
    EntityMap entityMap_; // entity map
    using EntityTypeMap = std::unordered_map<EntityType, EntityMap>;
    EntityTypeMap entityTypeMap_; // {entity type, EntityMap}
    using DeadEntityList = std::list<Entity*>;
    DeadEntityList deadEntityList_; // waiting for destructions

public:
    // dtor
    ~EntityManager();
    // Create entity with type T
    template <typename T, typename...Args>
    T* createEntity(Args...args) {
        return EntityFactoryRef(T).createEntity(args...);
    }
    // Create entity with type T
    template <typename T, typename...Args>
    T* createEntityByType(EntityType type, Args...args) {
        return EntityFactoryRef(T).createEntityByType(type, args...);
    }
    // Create a entity with type T and resolve it with entity ID
    template <typename T, typename...Args>
    T* createEntityAndResolve(EntityID entityID, Args...args) {
        auto entity = createEntity<T>(args...);
        if (entity) {
            entity->resolve(entityID);
        }
        return entity;
    }
    // Returns entity pointer
    // @param entityID entity ID
    // @retval nullptr not found
    // @retval entity pointer
    Entity* getEntity(EntityID entityID);
    // Returns entity pointer
    // @param entityID entity ID
    // @retval nullptr not found
    // @retval entity pointer
    template <typename T>
    T* getEntityByType(EntityID entityID) {
        auto entity = getEntity(entityID);
        if (!entity) {
            return nullptr;
        }
        return dynamic_cast<T*>(entity);
    }    
    // Returns the entity by type
    const EntityMap& getEntities(EntityType entityType);
    // Dispatch entity message
    // @param entityID entity ID
    // @param msg google::protobuf::Message
    // @retval true
    // @retval false fail
    template <typename Msg>
    bool onMessage(EntityID from, EntityID to, const Msg& msg) {
        auto msgID = ProtocolRegisterRef.getMessageIDFromMessage(msg);
        if (!msgID) {
            errorlog << "Register message handler for entity failed, message name:"
                << msg.GetDescriptor()->name() << endlog;
            return false;
        }
        auto entityIt = entityMap_.find(to);
        if (entityIt != entityMap_.end()) {
            auto method = entityIt->second->getHandlerByMessage(msg);
            if (method) {
                try {
                    method(from, msg);
                } catch (std::exception& e) {
                    errorlog << "Exception caught, when process entity message, MSGID:"
                        << msgID << ", Entity ID:" << to << ", Exception:" << e.what() << endlog;
                }
                return true;
            }
        }
        errorlog << "Entity message not found, message name:" << msg.GetDescriptor()->name() << endlog;
        return false;
    }
    // update cycle
    void update();

private:
    // Add entity to entity map and remove from pending set
    // @param entity entity pointer
    // @retval true
    // @retval false fail
    bool addEntity(Entity* entity);
    // Remove entity
    // @param entity entity pointer
    // @retval true
    // @retval false fail
    bool destroyEntity(Entity* entity);

    friend class Entity;
};

// Returns entity pointer
// @param entityID entity ID
// @retval nullptr not found
// @retval entity pointer
template <>
inline Entity* EntityManager::getEntityByType<Entity>(EntityID entityID) {
    return getEntity(entityID);
}

#define EntityManagerRef (*kratos::corelib::Singleton<EntityManager>::instance())
#define EntityManagerSingleton kratos::corelib::Singleton<EntityManager>

// Creates a new entity with type T and invokes the constructor of T with parameter args...
template <typename T, typename...Args>
T* createEntity(Args...args) {
    return EntityManagerRef.createEntity<T>(args...);
}

// Creates a new entity with type T and invokes the constructor of T with parameter args...
template <typename T, typename...Args>
T* createEntityByType(EntityType type, Args...args) {
    return EntityManagerRef.createEntityByType<T>(type, args...);
}

// Creates a new entity with type T and invokes the constructor of T with parameter args...
// The new created one is resolved with given entityID
template <typename T, typename...Args>
T* createEntityAndResolve(EntityID entityID, Args...args) {
    return EntityManagerRef.createEntityAndResolve<T>(args...);
}

// Returns entity pointer which is identified by entityID with type T
template <typename T>
T* getEntity(EntityID entityID) {
    return EntityManagerRef.getEntityByType<T>(entityID);
}
